06 异常处理
程序运行时可能出现文件不存在、网络断开、输入非法等问题。异常处理用于捕获并处理这些错误,避免程序在未处理异常时直接终止。try/except用于捕获异常,raise用于主动抛出异常。
一、try/except
1.1 基本用法
python
>>> while True:
... try:
... x = int(input("请输入一个数字: "))
... break
... except ValueError:
... print("无效数字,请重试!")如果try中的代码发生ValueError,程序会跳转到对应的except分支执行。
1.2 捕获多种异常
python
>>> try:
... # 可能出错的代码
... result = 1 / 0
... except ZeroDivisionError:
... print("除以零")
... except (ValueError, TypeError):
... print("值错误或类型错误")一个try可以对应多个except,分别处理不同类型的异常。也可以使用元组捕获多种异常。
1.3 捕获异常信息
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError as e:
... print(f"错误: {e}")
...
错误: division by zero二、else子句
else会在try代码块没有发生异常时执行:
python
>>> try:
... f = open('workfile', encoding='utf-8')
... except OSError:
... print("无法打开文件")
... else:
... content = f.read()
... f.close()
... print(f"文件有{len(content)}个字符")else可以将可能出错的代码和正常逻辑分开,减少意外捕获无关异常的风险。
三、finally子句
finally无论是否发生异常都会执行,通常用于清理资源:
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError:
... print("除以零")
... finally:
... print("这段总是会执行")完整的try语句:try → except → else → finally。
四、raise抛出异常
4.1 基本用法
python
>>> def divide(a, b):
... if b == 0:
... raise ValueError("除数不能为零")
... return a / b
...
>>> divide(1, 0)
ValueError: 除数不能为零4.2 重新抛出
python
>>> try:
... result = 1 / 0
... except ZeroDivisionError:
... print("记录错误日志")
... raise # 重新抛出当前异常raise不带参数会重新抛出当前正在处理的异常。
4.3 异常链
python
>>> def func():
... try:
... result = 1 / 0
... except ZeroDivisionError as e:
... raise RuntimeError("计算失败") from e
...
>>> func()
Traceback:
...
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
...
RuntimeError: 计算失败from用于指定原始异常,形成异常链,便于调试。
五、自定义异常
5.1 定义异常类
python
>>> class MyError(Exception):
... pass
...
>>> raise MyError("出错了")
MyError: 出错了异常类通常继承Exception,也可以添加自定义属性:
python
>>> class ValidationError(Exception):
... def __init__(self, field, message):
... self.field = field
... self.message = message
... super().__init__(f"{field}: {message}")
...
>>> raise ValidationError("email", "格式不正确")
ValidationError: email: 格式不正确5.2 异常层次
BaseException
+-- KeyboardInterrupt
+-- SystemExit
+-- Exception
+-- ValueError
+-- TypeError
+-- RuntimeError
+-- OSError
| +-- FileNotFoundError
| +-- PermissionError
+-- 自定义异常自定义异常通常应继承Exception,不应继承BaseException。
六、ExceptionGroup(3.11+)
6.1 基本概念
ExceptionGroup用于同时抛出多个异常:
python
>>> def collect_errors():
... errors = []
... try:
... int("abc")
... except ValueError as e:
... errors.append(e)
... try:
... 1 / 0
... except ZeroDivisionError as e:
... errors.append(e)
... if errors:
... raise ExceptionGroup("多个错误", errors)
...
>>> collect_errors()
+ Exception Group Traceback (most recent call last):
| ...
| ExceptionGroup: 多个错误 (2 sub-exceptions)
+-+---------------- 1 ----------------
| ValueError: invalid literal for int() with base 10: 'abc'
+---------------- 2 ----------------
| ZeroDivisionError: division by zero
+------------------------------------6.2 except*匹配
except*用于匹配ExceptionGroup中的特定异常:
python
>>> try:
... raise ExceptionGroup("错误", [
... ValueError("无效值"),
... TypeError("类型错误"),
... FileNotFoundError("文件不存在"),
... ])
... except* ValueError as eg:
... print(f"值错误: {eg.exceptions}")
... except* (TypeError, FileNotFoundError) as eg:
... print(f"其他错误: {eg.exceptions}")except*可以匹配多次,每次处理一个子组。
七、实用模式
7.1 忽略异常
python
>>> try:
... value = config["key"]
... except KeyError:
... value = default_value
>>> # 或者用dict.get(),但try/except更通用7.2 清理资源
python
>>> f = open('workfile', 'w')
>>> try:
... f.write('data')
... # ... 更多操作
... finally:
... f.close() # 无论是否出错都关闭也可以使用with简化资源管理:
python
>>> with open('workfile', 'w') as f:
... f.write('data')7.3 断言(assert)
python
>>> def calculate_average(numbers):
... assert len(numbers) > 0, "列表不能为空"
... return sum(numbers) / len(numbers)assert用于开发阶段检查不变量,不应作为用户输入校验机制。使用-O参数运行 Python 时,assert 会被禁用。
八、总结
| 语句 | 用途 |
|---|---|
try/except | 捕获异常 |
try/except/else | 没异常时执行else |
try/finally | 总是执行清理代码 |
raise | 抛出异常 |
raise ... from ... | 异常链 |
ExceptionGroup | 同时抛出多个异常(3.11+) |
except* | 匹配异常组(3.11+) |
assert | 开发时检查条件 |
处理文件等资源时,with语句通常比手写try/finally更简洁。自定义异常应继承Exception,不应继承BaseException。